#ifndef _SE_OPENGLBASE_
#define _SE_OPENGLBASE_

#include "Core/GlobalContext.h"

#include <glad/glad.h>

namespace SE
{
	inline void ClearErrors()
	{
		while (glGetError()) {}
	}

	inline U32 GetErrorCode()
	{
		return glGetError();
	}

	inline String HandleError(const U32 errorCode, const char* file, const I32 line) {
		StringStream ss;
		ss << "ErrorCode:" << errorCode << '\n'
		   << "File:" << file << '\n'
		   << "Line:" << line << '\n';
		return ss.str();
	}

	//--------------------------------------------------------------------------------------------------------------------------------------
	/* Convert functions */
	inline GLenum ToGLenum(const ShaderDataType type)
	{
		switch (type)
		{
			default: SE_ASSERT(false, "Unknown ShaderDataType!"); return GL_NONE;
			case ShaderDataType::Float:    return GL_FLOAT;
			case ShaderDataType::Float2:   return GL_FLOAT;
			case ShaderDataType::Float3:   return GL_FLOAT;
			case ShaderDataType::Float4:   return GL_FLOAT;
			case ShaderDataType::Mat3:     return GL_FLOAT;
			case ShaderDataType::Mat4:     return GL_FLOAT;
			case ShaderDataType::Int:      return GL_INT;
			case ShaderDataType::Int2:     return GL_INT;
			case ShaderDataType::Int3:     return GL_INT;
			case ShaderDataType::Int4:     return GL_INT;
			case ShaderDataType::Bool:     return GL_BOOL;
		}
	}

	inline GLenum ToGLenum(const RenderInternalFormat format)
	{
		switch (format)
		{
			default: return GL_NONE;
			case RenderInternalFormat::R8: return GL_R8;
			case RenderInternalFormat::RG8: return GL_RG8;
			case RenderInternalFormat::RGB8: return GL_RGB8;
			case RenderInternalFormat::RGBA8: return GL_RGBA8;
			case RenderInternalFormat::RGBA32F: return GL_RGBA32F;
			case RenderInternalFormat::Depth32F: return GL_DEPTH_COMPONENT32F;
			case RenderInternalFormat::Stencil32F: return GL_STENCIL_INDEX;  // ?
			case RenderInternalFormat::Depth24FStencil8F: return GL_DEPTH24_STENCIL8;
		}
	}

	inline RenderInternalFormat ToInternalFormat(const GLenum format)
	{
		switch (format)
		{
			default: return RenderInternalFormat::None;
			case GL_R8: return RenderInternalFormat::R8;
			case GL_RG8: return RenderInternalFormat::RG8;
			case GL_RGB8: return RenderInternalFormat::RGB8;
			case GL_RGBA8: return RenderInternalFormat::RGBA8;
			case GL_RGBA32F: return RenderInternalFormat::RGBA32F;
			case GL_DEPTH_COMPONENT32F: return RenderInternalFormat::Depth32F;
			case GL_STENCIL_INDEX: return RenderInternalFormat::Stencil32F;  // ?
			case GL_DEPTH24_STENCIL8:return RenderInternalFormat::Depth24FStencil8F;
		}
	}

	inline GLenum ToGLenum(const RenderDataFormat format)
	{
		switch (format)
		{
			default: return GL_NONE;
			case RenderDataFormat::R: return GL_RED;
			case RenderDataFormat::RG: return GL_RG;
			case RenderDataFormat::RGB: return GL_RGB;
			case RenderDataFormat::RGBA: return GL_RGBA;
			case RenderDataFormat::Depth: return GL_DEPTH_COMPONENT;
			case RenderDataFormat::Stencil: return GL_STENCIL_INDEX;  // ?
			case RenderDataFormat::DepthAndStencil: return GL_DEPTH_STENCIL;
		}
	}

	inline RenderDataFormat ToDataFormat(const GLenum format)
	{
		switch (format)
		{
			default: return RenderDataFormat::None;
			case GL_RED: return RenderDataFormat::R;
			case GL_RG: return RenderDataFormat::RG;
			case GL_RGB: return RenderDataFormat::RGB;
			case GL_RGBA: return RenderDataFormat::RGBA;
			case GL_DEPTH_COMPONENT: return RenderDataFormat::Depth;
			case GL_STENCIL_INDEX: return RenderDataFormat::Stencil;  // ?
			case GL_DEPTH_STENCIL: return RenderDataFormat::DepthAndStencil;
		}
	}

	inline RenderDataFormat ToDataFormat(const RenderInternalFormat format)
	{
		switch (format)
		{
			default: return RenderDataFormat::None;
			case RenderInternalFormat::R8: return RenderDataFormat::R;
			case RenderInternalFormat::RG8: return RenderDataFormat::RG;
			case RenderInternalFormat::RGB8: return RenderDataFormat::RGB;
			case RenderInternalFormat::RGBA8: return RenderDataFormat::RGBA;
			case RenderInternalFormat::RGBA32F: return RenderDataFormat::RGBA;
			case RenderInternalFormat::Depth32F: return RenderDataFormat::Depth;
			case RenderInternalFormat::Stencil32F: return RenderDataFormat::Stencil;
			case RenderInternalFormat::Depth24FStencil8F: return RenderDataFormat::DepthAndStencil;
		}
	}

	inline GLenum ToGLenum(const TextureBuffer::WrapMode mode)
	{
		switch (mode)
		{
			default: return GL_NONE;
			case TextureBuffer::WrapMode::Repeat: return GL_REPEAT;
			case TextureBuffer::WrapMode::MirroredRepeat: return GL_MIRRORED_REPEAT;
		}
	}

	inline GLenum ToGLenum(const TextureBuffer::MinFilterMode mode)
	{
		switch (mode)
		{
			default: return GL_NONE;
			case TextureBuffer::MinFilterMode::Linear: return GL_LINEAR;
			case TextureBuffer::MinFilterMode::Nearest: return GL_NEAREST;
		}
	}

	inline GLenum ToGLenum(const TextureBuffer::MagFilterMode mode)
	{
		switch (mode)
		{
			default: return GL_NONE;
			case TextureBuffer::MagFilterMode::Linear: return GL_LINEAR;
			case TextureBuffer::MagFilterMode::Nearest: return GL_NEAREST;
		}
	}

	inline GLenum ToGLenum(const FrameBuffer::AttachmentType type)
	{
		switch (type)
		{
			default: return GL_NONE;
			case FrameBuffer::AttachmentType::Color: return GL_COLOR_ATTACHMENT0;
			case FrameBuffer::AttachmentType::Depth: return GL_DEPTH_ATTACHMENT;
			case FrameBuffer::AttachmentType::Stencil: return GL_STENCIL_ATTACHMENT;
			case FrameBuffer::AttachmentType::DepthAndStencil: return GL_DEPTH_STENCIL_ATTACHMENT;
		}
	}

	inline GLenum ToGLenum(const FrameBuffer::AttachmentBufferType type)
	{
		switch (type)
		{
			default: return GL_NONE;
			case FrameBuffer::AttachmentBufferType::Texture: return GL_TEXTURE_2D;
			case FrameBuffer::AttachmentBufferType::RenderBufferObject: return GL_RENDERBUFFER;
		}
	}

	inline GLenum ToGLenum(const RenderAPI::EnabledTest enabledTests)
	{
		GLenum tests = GL_NONE;
		if (RenderAPI::EnabledTest::Blend & enabledTests) tests |= GL_BLEND;
	}
	//--------------------------------------------------------------------------------------------------------------------------------------

	#ifdef SE_ENABLE_ASSERT
		#define GLCALL(x) \
			{ ClearErrors(); } x; \
			{ const auto _errorCode = GetErrorCode(); \
			if (GL_NO_ERROR != _errorCode) LOG_ERROR(HandleError(_errorCode, __FILE__, __LINE__)); }
	#else
		#define GLCALL(x) x
	#endif // !SE_ENABLE_ASSERT
} // !namespace SE

#endif // !_SE_OPENGLBASE_